package org.lex.swing.maze;

import java.awt.Point;
import java.util.Random;

public class DefaultMazeModel implements MazeModel {
	private int[][] data = null;

	private Point entry = null;
	private Point exit = null;

	public DefaultMazeModel(int row, int column) {
		super();
		this.data = new int[row][column];
		this.entry = new Point();
		this.exit = new Point();
		this.shuffle();
	}

	public void shuffle() {
		MazeFactory.shuffle(this);
	}

	public void set(int row, int column, int value) {
		this.data[row][column] = value;
	}

	public int get(int row, int column) {
		return this.data[row][column];
	}

	public int getColumnCount() {
		return this.data[0].length;
	}

	public int getRowCount() {
		return this.data.length;
	}

	public Point getEntry() {
		return this.entry;
	}

	public Point getExit() {
		return this.exit;
	}

	// DefaultMazeModel Factory
	private static class MazeFactory {
		private static Random rand = new Random();

		public static MazeModel shuffle(MazeModel mazeModel) {
			// Create a random maze. The strategy is to start with
			// a grid of disconnnected "rooms" separated by walls.
			// then look at each of the separating walls, in a random
			// order. If tearing down a wall would not create a loop
			// in the maze, then tear it down. Otherwise, leave it in place.
			int rowCount = mazeModel.getRowCount();
			int columnCount = mazeModel.getColumnCount();
			int row, col;
			int roomNo = 0; // number of rooms
			int wallNo = 0; // number of walls
			int[] wallrow = new int[(rowCount * columnCount) / 2]; // position
			// of walls
			// between rooms
			int[] wallcol = new int[(rowCount * columnCount) / 2];
			for (row = 0; row < rowCount; row++)
				// start with everything being a wall
				for (col = 0; col < columnCount; col++)
					mazeModel.set(row, col, MazeModel.WALL);
			for (row = 1; row < rowCount - 1; row += 2)
				// make a grid of empty rooms
				for (col = 1; col < columnCount - 1; col += 2) {
					// each room is represented by a different negative number
					roomNo++;
					mazeModel.set(row, col, -roomNo);
					if (row < rowCount - 2) { // record info about wall below
						// this room
						wallrow[wallNo] = row + 1;
						wallcol[wallNo] = col;
						wallNo++;
					}
					if (col < columnCount - 2) { // record info about wall to
						// right of this room
						wallrow[wallNo] = row;
						wallcol[wallNo] = col + 1;
						wallNo++;
					}
				}
			int r = 0;
			for (row = wallNo - 1; row > 0; row--) {
				// choose a wall randomly and maybe tear it down
				r = rand.nextInt(row);
				MazeFactory.tearDown(mazeModel, wallrow[r], wallcol[r]);
				wallrow[r] = wallrow[row];
				wallcol[r] = wallcol[row];
			}
			// replace negative values in maze[][] with empty
			for (row = 1; row < rowCount - 1; row++)
				for (col = 1; col < columnCount - 1; col++)
					if (mazeModel.get(row, col) < 0)
						mazeModel.set(row, col, MazeModel.CORRIDORS);
			// generate random entry & exit
			randomPoint(mazeModel, mazeModel.getEntry());
			randomPoint(mazeModel, mazeModel.getExit());
			return mazeModel;
		}

		private static void randomPoint(MazeModel mazeModel, Point p) {
			p.x = rand.nextInt(mazeModel.getColumnCount());
			p.y = rand.nextInt(mazeModel.getRowCount());
			if (MazeModel.CORRIDORS != mazeModel.get(p.x, p.y))
				randomPoint(mazeModel, p);
		}

		private static void tearDown(MazeModel mazeModel, int row, int col) {
			// Tear down a wall, unless doing so will form a loop. Tearing down
			// a wall
			// joins two "rooms" into one "room". (Rooms begin to look like
			// corridors
			// as they grow.) When a wall is torn down, the room codes on one
			// side are
			// converted to match those on the other side, so all the cells in a
			// room
			// have the same code. Note that if the room codes on both sides of
			// a
			// wall already have the same code, then tearing down that wall
			// would
			// create a loop, so the wall is left in place.
			if (row % 2 == 1 && (mazeModel.get(row, col - 1) != mazeModel.get(row, col + 1))) {
				// row is odd; wall separates rooms horizontally
				fill(mazeModel, row, col - 1, mazeModel.get(row, col - 1), mazeModel.get(row, col + 1));
				mazeModel.set(row, col, mazeModel.get(row, col + 1));
			} else if (row % 2 == 0 && (mazeModel.get(row - 1, col) != mazeModel.get(row + 1, col))) {
				// row is even; wall separates rooms vertically
				fill(mazeModel, row - 1, col, mazeModel.get(row - 1, col), mazeModel.get(row + 1, col));
				mazeModel.set(row, col, mazeModel.get(row + 1, col));
			}
		}

		private static void fill(MazeModel mazeModel, int row, int col, int replace, int replaceWith) {
			// called by tearDown() to change "room codes".
			if (mazeModel.get(row, col) == replace) {
				mazeModel.set(row, col, replaceWith);
				fill(mazeModel, row + 1, col, replace, replaceWith);
				fill(mazeModel, row - 1, col, replace, replaceWith);
				fill(mazeModel, row, col + 1, replace, replaceWith);
				fill(mazeModel, row, col - 1, replace, replaceWith);
			}
		}
	}
}
